package com.fsh.lingsp.common.websocket;


import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.spring.SpringUtil;
import cn.hutool.json.JSONUtil;
import com.fsh.lingsp.common.websocket.domain.enums.WSReqTypeEnum;
import com.fsh.lingsp.common.websocket.domain.vo.request.WSBaseReq;
import com.fsh.lingsp.common.websocket.service.WebSocketService;
import com.fsh.lingsp.common.websocket.util.NettyUtil;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandler.Sharable;
import io.netty.channel.ChannelHandlerContext;
import io.netty.channel.SimpleChannelInboundHandler;
import io.netty.handler.codec.http.websocketx.TextWebSocketFrame;
import io.netty.handler.codec.http.websocketx.WebSocketServerProtocolHandler;
import io.netty.handler.timeout.IdleState;
import io.netty.handler.timeout.IdleStateEvent;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;


/**
 * 作者：fsh
 * 日期：2024/02/22
 * <p>
 * 描述：Netty Web 套接字服务器处理程序
 *
 * 定义一个名为NettyWebSocketServerHandler的公共类，
 *    它继承自SimpleChannelInboundHandler<TextWebSocketFrame>。
 * SimpleChannelInboundHandler是Netty提供的一个基础类，用于处理入站的消息，
 *    而TextWebSocketFrame是WebSocket的文本消息类型。
 */
@Slf4j
@Sharable // 使用@Sharable注解标记这个类是可以被多个线程共享的。
public class NettyWebSocketServerHandler extends SimpleChannelInboundHandler<TextWebSocketFrame> {


    private WebSocketService webSocketService;

    /**
     * 它会在一个新的连接被建立并准备好时调用。
     * 换句话说，当Netty接受到一个新的客户端连接时，这个方法会被触发。
     *
     * 只要建立了连接，就要存起来这个channel
     */
    @Override
    public void channelActive(ChannelHandlerContext ctx) throws Exception {
        //获取 WebSocketService 实例
        webSocketService=SpringUtil.getBean(WebSocketService.class);
        // 保存channel到map中。 管理所有用户的连接，包括登录态和游客
        webSocketService.connect(ctx.channel());
    }

    private WebSocketService getService() {
        return SpringUtil.getBean(WebSocketService.class);
    }

    // 当web客户端连接后，触发该方法
    @Override
    public void handlerAdded(ChannelHandlerContext ctx) throws Exception {
        this.webSocketService = getService();
    }

    // 客户端离线
    @Override
    public void handlerRemoved(ChannelHandlerContext ctx) throws Exception {
        userOffLine(ctx);
    }



    /**
     * 这是Netty框架中的一个方法，用于处理特殊的事件。
     * 当WebSocket服务器与客户端完成握手时，这个方法会被触发。
     * 定义一个名为userEventTriggered的方法，
     *      该方法接受两个参数：一个ChannelHandlerContext对象和一个Object对象
     */
    @Override
    public void userEventTriggered(ChannelHandlerContext ctx, Object evt) throws Exception {
        if (evt instanceof WebSocketServerProtocolHandler.HandshakeComplete){
            log.info("NettyWebSocketServerHandler.userEventTriggered()===>握手完成，channel={}",ctx.channel());
            //ws第一次连接就在url上传了参数token，token被其他代码放到了channel的附件上
            //现在，从channel的attr中取出token
            String token = NettyUtil.getAttr(ctx.channel(), NettyUtil.TOKEN);
            if(StrUtil.isNotBlank(token)){
                webSocketService.authorize(ctx.channel(),token);
            }
        }else if(evt instanceof IdleStateEvent){
            IdleStateEvent event = (IdleStateEvent) evt;
            if (event.state()== IdleState.READER_IDLE){
                log.info("NettyWebSocketServerHandler.userEventTriggered()===>读空闲，用户下线，channel={}",ctx.channel());
                // 1、读空闲 用户下线 ，关闭连接
                userOffLine(ctx);
            }
        }
    }

    /**
     * 用户下线统一处理
     */
    private void userOffLine(ChannelHandlerContext ctx) {
        //调用方法，移除channel
        webSocketService.removed(ctx.channel());
        //关闭channel
        ctx.channel().close();
    }

    /**
     * 客户端主动下线：
     *  当一个客户端与服务器的连接断开时，这个方法会被触发。
     *  例如释放资源、发送断开连接的通知给其他客户端
     */
    @Override
    public void channelInactive(ChannelHandlerContext ctx) throws Exception {
        log.info("NettyWebSocketServerHandler.channelInactive()===>客户端主动下线,{}",ctx.channel());
        userOffLine(ctx);
    }

    /**
     * 重写父类SimpleChannelInboundHandler的channelRead0方法，该方法在接收到入站的TextWebSocketFrame时被调用。
     *
     * @param context 通道处理程序上下文。
     *       提供了对当前处理链的引用，用于在处理器之间传递数据或执行各种操作。
     * @param frame    文本 Web 套接字框架.
     *       包含了从客户端发送过来的WebSocket文本消息。
     */
    @Override
    protected void channelRead0(ChannelHandlerContext context,
                                TextWebSocketFrame frame) throws Exception {
        // 当服务器接收到WebSocket文本消息时，在这里添加处理逻辑，如解析消息、处理业务逻辑、返回响应等

        // 获取消息
        String text = frame.text();
        // 转化为 WSBaseReq实体
        WSBaseReq wsBaseReq = JSONUtil.toBean(text, WSBaseReq.class);
        // 根据前端请求的type执行不同的处理动作
        switch (WSReqTypeEnum.of(wsBaseReq.getType())) {
            case LOGIN:
                System.out.println("请求二维码");
                webSocketService.handleLoginReq(context.channel());
                break;
            case AUTHORIZE:
                System.out.println("登录认证");
                webSocketService.authorize(context.channel(),wsBaseReq.getData());
                break;
            case HEARTBEAT:
                System.out.println("心跳");
                break;
        }
    }

}
